use crate::{
    driver::cmd_uart::CMD_UART, error::AppErr, proto::{make_res_empty, CmdFrame, TYPE_SIMPLE_RES}, utils::{bootload_with_disable_irq, OtaInfo}, APP_ORIGIN, BOOT_ORIGIN, OTA_ORIGIN
};
use core::{ops::Deref, slice};
use mlibs::{
    cmsis_rtos2::{kernel::os_delay, utils::Mutable},
    periph::flash,
};

// #[derive(Deserialize)]
// struct StartReq {

//     pkg_size: usize,
// }

type StartReq = usize;
// #[derive(Deserialize)]
// struct TranslateReq<'a> {
//     id: usize,
//     pkg: &'a [u8],
// }
type TranslateReq<'a> = (usize, &'a [u8]);
// #[derive(Deserialize)]
// struct CompleteReq<'a> {
//     md5: &'a [u8],
// }

type CompleteReq<'a> = &'a [u8];

pub const OTA_DATA_ADDR: u32 = OTA_ORIGIN;

struct OtaCtx {
    pkg_size: usize,
    pkg_index: usize,
    pkg_id: usize,
}

static OTA_INFO: Mutable<Option<OtaCtx>> = Mutable::const_new(None);

impl OtaCtx {
    fn init(pkg_size: usize) -> Self {
        Self {
            pkg_size,
            pkg_index: 0,
            pkg_id: 0,
        }
    }
}

pub fn on_start(frame: &CmdFrame) -> Result<(), AppErr> {
    let req: StartReq = frame.parse()?;

    flash::erase(OTA_DATA_ADDR, req)?;
    *OTA_INFO.mutable() = Some(OtaCtx::init(req));

    CMD_UART.write(&make_res_empty(TYPE_SIMPLE_RES, frame.seq(), 0));

    Ok(())
}

pub fn on_translate(frame: &CmdFrame) -> Result<(), AppErr> {
    let Some(info) = OTA_INFO.mutable() else {
        return Err(AppErr::Other("invalid request"));
    };

    let req: TranslateReq = frame.parse()?;

    if req.0 != info.pkg_id {
        return Err(AppErr::Other("pkg_id invalid"));
    }

    flash::write(
        OTA_DATA_ADDR + info.pkg_index as u32,
        req.1.as_ptr(),
        req.1.len(),
    )?;
    info.pkg_index += req.1.len();
    info.pkg_id += 1;

    CMD_UART.write(&make_res_empty(TYPE_SIMPLE_RES, frame.seq(), 0));
    Ok(())
}

pub fn on_complete(frame: &CmdFrame) -> Result<(), AppErr> {
    let Some(info) = OTA_INFO.mutable() else {
        return Err(AppErr::Other("invalid request2"));
    };

    if info.pkg_index != info.pkg_size {
        return Err(AppErr::Other("pkg_index != pkg_size"));
    }

    let firm = unsafe { slice::from_raw_parts(OTA_DATA_ADDR as *const u8, info.pkg_size) };

    let req: CompleteReq = frame.parse()?;

    let ret = md5::compute(firm);

    if ret.deref() != req {
        return Err(AppErr::Other("md5 invalid"));
    }

    CMD_UART.write(&make_res_empty(TYPE_SIMPLE_RES, frame.seq(), 0));

    os_delay(100);

    OtaInfo.save(OTA_DATA_ADDR, info.pkg_size, APP_ORIGIN);

    bootload_with_disable_irq(BOOT_ORIGIN);

    Ok(())
}
